using System;
using System.Collections.Generic;
using System.Text;
using NBusiness.CodeDom;
using NBusiness.ESharp.Compiler;
using NBusiness.CodeDom.Compiler;
using NBusiness.Utilities;
using NBusiness.Templates;

namespace NBusiness.ESharp
{
    [Serializable]
    public class ESharpEntity : Entity
    {
        #region Find helpers
        internal IValidatable[] FindValidatable(string name)
        {

            IValidatable[] foundFields = this.Fields.FindAll(new Predicate<EntityField>(
                delegate(EntityField field)
                {
                    return field.Name == name;
                })).ToArray();

            IValidatable[] foundRelationship = this.Relationships.FindAll(new Predicate<EntityRelationship>(
                delegate(EntityRelationship relationship)
                {
                    return relationship.Name == name;
                })).ToArray();

            List<IValidatable> found = new List<IValidatable>();
            found.AddRange(foundFields);
            found.AddRange(foundRelationship);

            return found.ToArray();
        }
        #endregion

        #region Attach
        protected internal override void Attach()
        {
            foreach (EntityRelationship relationship in Relationships)
            {
                if(relationship.From == this)
                    relationship.Attach();
            }

            foreach (EntityRule rule in Rules)
                rule.Attach();

            foreach (EntityElement element in Elements)
            {
                if (element.Type == EntityElementType.EntityTemplate)
                {
                    string templateName = element.Value;
                    Type templateType = null;
                    if ((Parent as EntityFamily).Root.Using.ContainsKey(element.Path))
                    {
                        foreach (EntityUsing eu in (Parent as EntityFamily).Root.Using[element.Path])
                        {
                            Type t = eu.FindType(element.Value, typeof(ITemplate));
                            if (t == null)
                                t = eu.FindType(element.Value + "Template", typeof(ITemplate));

                            if (t != null && templateType == null)
                            {
                                templateType = t;
                            }
                            else if (t != null && templateType != null && t != templateType)
                            {
                                OnBuildError(new ESharpBuildError(ESharpErrorCode.ES1004, element));
                                templateType = null;
                                break;
                            }
                        }
                    }

                    if (templateType != null)
                    {
                        EntityTemplate template = null;

                        EntityRoot root = (this.Parent as EntityFamily).Root;
                        if (root.Templates.ContainsKey(templateType))
                        {
                            template = root.Templates[templateType];
                        }
                        else
                        {
                            template = new EntityTemplate(
                             element.Value,
                             templateType.FullName,
                             templateType.Assembly.FullName);
                            root.Templates.Add(templateType, template);
                        }

                        template.Entities.Add(this);
                        this.As.Add(template);

                    }
                    else OnBuildError(new ESharpBuildError(ESharpErrorCode.ES1000, element));
                }
            }
        } 
        #endregion

        #region Initialize Assignment logic
        protected override void InitializeAssignmentLogic()
        {
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.Identifier,
                EntityElementType.EntityName,
                new AssignmentLogicHandler(AssignEntityName),
                EntityElementType.EntityDeclaration));

            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.GroupBegin,
                EntityElementType.EntityBegin,
                EntityElementType.EntityName,
                EntityElementType.EntityTemplate));

            //Entity template, will be attached to the template later.
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.EntityAs,
                EntityElementType.EntityAs,
                EntityElementType.EntityName));
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.Identifier,
                EntityElementType.EntityTemplate,
                EntityElementType.EntityAs,
                EntityElementType.EntityTemplateSeparator));
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.EntityTemplateSeparator,
                EntityElementType.EntityTemplateSeparator,
                EntityElementType.EntityTemplate));

            //Create "field"
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.FieldDeclaration,
                EntityElementType.FieldDeclaration,
                new AssignmentLogicHandler(CreateNewField),
                EntityElementType.EntityBegin,
                EntityElementType.StatementEnd));

            //Create "access" rule
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.AccessDeclaration,
                EntityElementType.AccessDeclaration,
                new AssignmentLogicHandler(CreateAccessRule),
                EntityElementType.FamilyBegin,
                EntityElementType.StatementEnd));

            //Create "validation" rule
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.ValidateDeclaration,
                EntityElementType.ValidateDeclaration,
                new AssignmentLogicHandler(CreateValidationRule),
                EntityElementType.FamilyBegin,
                EntityElementType.StatementEnd));

            //Create "authorize" rule
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.AuthorizeDeclaration,
                EntityElementType.AuthorizeDeclaration,
                new AssignmentLogicHandler(CreateAuthorizeRule),
                EntityElementType.FamilyBegin,
                EntityElementType.StatementEnd));

            //Create "action" 
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.ActionDeclaration,
                EntityElementType.ActionDeclaration,
                new AssignmentLogicHandler(CreateAction),
                EntityElementType.FamilyBegin,
                EntityElementType.StatementEnd));

            //Create "relationship"
            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.RelationshipDeclaration,
                EntityElementType.RelationshipDeclaration,
                new AssignmentLogicHandler(CreateRelationship),
                EntityElementType.FamilyBegin,
                EntityElementType.StatementEnd));

            Assignments.Add(new AssignmentLogic(
                ESharpParser.Expressions.GroupEnd,
                EntityElementType.EntityEnd,
                PostAssignmentAction.Stop,
                EntityElementType.StatementEnd,
                EntityElementType.EntityBegin));
        }
        #endregion

        #region Do Assignments
        private void AssignEntityName(object sender, AssignmentLogicEventArgs e)
        {
            Name = e.Elements.Peek().Value;
        }

        private void CreateNewField(object sender, AssignmentLogicEventArgs e)
        {
            ESharpField f = new ESharpField();
            Fields.Add(f);
            f.Parent = this;
            f.BuildError += new EventHandler<EntityBuildErrorEventArgs>(child_ParseError);
            f.Build(e.Elements, EntityElementType.FieldDeclaration);
        }

        private void CreateAccessRule(object sender, AssignmentLogicEventArgs e)
        {
            ESharpAccess rule = new ESharpAccess();
            rule.Parent = this;
            Rules.Add(rule);
            rule.Parent = this;
            rule.BuildError += new EventHandler<EntityBuildErrorEventArgs>(child_ParseError);
            rule.Build(e.Elements, EntityElementType.AccessDeclaration);
        }
        private void CreateValidationRule(object sender, AssignmentLogicEventArgs e)
        {
            ESharpValidation rule = new ESharpValidation();
            rule.Parent = this;
            rule.BuildError += new EventHandler<EntityBuildErrorEventArgs>(child_ParseError);
            Rules.Add(rule);
            rule.Parent = this;
            rule.Build(e.Elements, EntityElementType.ValidateDeclaration);
        }

        private void CreateAuthorizeRule(object sender, AssignmentLogicEventArgs e)
        {
            ESharpAuthorization rule = new ESharpAuthorization();
            rule.Parent = this;
            rule.BuildError += new EventHandler<EntityBuildErrorEventArgs>(child_ParseError);
            Rules.Add(rule);
            rule.Build(e.Elements, EntityElementType.AuthorizeDeclaration);
        }

        private void CreateAction(object sender, AssignmentLogicEventArgs e)
        {
            ESharpAction a = new ESharpAction();
            a.BuildError += new EventHandler<EntityBuildErrorEventArgs>(child_ParseError);
            Actions.Add(a);
            a.Build(e.Elements, EntityElementType.ActionDeclaration);
        }

        private void CreateRelationship(object sender, AssignmentLogicEventArgs e)
        {
            ESharpRelationship a = new ESharpRelationship();
            a.Parent = this;
            a.BuildError += new EventHandler<EntityBuildErrorEventArgs>(child_ParseError);
            a.Build(e.Elements, EntityElementType.RelationshipDeclaration);
            Relationships.Add(a);
            a.From = this;
        }

        void child_ParseError(object sender, EntityBuildErrorEventArgs e)
        {
            OnBuildError(e.Error);
        } 
        #endregion

        #region Get element from child
        protected override EntityElement GetElementFromChild(string path, int line, int col)
        {
            EntityElement e = null;
            foreach (EntityField field in Fields)
            {
                e = field.GetElementByLocation(path, line, col);
                if (e != null) break;
            }

            if (e == null)
            {
                foreach (EntityRule rule in Rules)
                {
                    e = rule.GetElementByLocation(path, line, col);
                    if (e != null) break;
                }
            }

            if (e == null)
            {
                foreach (EntityRelationship r in Relationships)
                {
                    e = r.GetElementByLocation(path, line, col);
                    if (e != null) break;
                }
            }

            if (e == null)
            {
                foreach (EntityAction action in Actions)
                {
                    e = action.GetElementByLocation(path, line, col);
                    if (e != null) break;
                }
            }

            return e;
        } 
        #endregion

        #region Get Unit from child
        protected override EntityCompileUnit GetUnitFromChild(EntityElement element)
        {
            EntityCompileUnit unit = null;

            foreach (EntityField field in Fields)
            {
                unit = field.GetUnitByElement(element);
                if (unit != null) break;
            }

            if (unit == null)
            {
                foreach (EntityRule rule in Rules)
                {
                    unit = rule.GetUnitByElement(element);
                    if (unit != null) break;
                }
            }
            if (unit == null)
            {
                foreach (EntityRelationship r in Relationships)
                {
                    unit = r.GetUnitByElement(element);
                    if (unit != null) break;
                }
            }
            if (unit == null)
            {
                foreach (EntityAction action in Actions)
                {
                    unit = action.GetUnitByElement(element);
                    if (unit != null) break;
                }
            }

            return unit;
        } 
        #endregion
    }
}
